﻿#region header

//  ***************************************************************************
// *                                LICENSE
//  ***************************************************************************
//  * "Haplous" .NET Simple 2D Game Framework
//  * 
//  * Copyright © Jonathan Evans 2014-2014
//  * 
//  * This program is free software; you can redistribute it and/or
//  * modify it under the terms of the GNU General Public License
//  * as published by the Free Software Foundation; either version 2
//  * of the License, or (at your option) any later version.
//  * 
//  * This program is distributed in the hope that it will be useful,
//  * but WITHOUT ANY WARRANTY; without even the implied warranty of
//  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  * GNU General Public License for more details.
//  * 
//  * You should have received a copy of the GNU General Public License
//  * along with this program; if not, write to the Free Software
//  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//  * 
//  ***************************************************************************
//  * 
//  * Contact: opencollar@googlemail.com
//  * 
//  * Website: http://haplous.codeplex.com/
//  *
//  * NuGet:   http://www.nuget.org/packages/haplous/
//  *
//  * File:    Haplous/Haplous/ElementBase.cs
//  *
//  ***************************************************************************

#endregion

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;

using Haplous.Core;
using Haplous.Correctness;

using JetBrains.Annotations;

namespace Haplous
{
    /// <summary>
    ///     The root class of all classes used to represent behavior or entities in a game.
    /// </summary>
    [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly")]
    [DebuggerDisplay("ElementBase: Name={Name}")]
    public abstract class ElementBase : DisposableNotifyObject, IElement
    {
        /// <summary>
        ///     The game to which the class belongs.
        /// </summary>
        [NotNull]
        private readonly Game _game;

        /// <summary>
        ///     Initializes a new instance of the <see cref="ElementBase" /> class.
        /// </summary>
        /// <param name="game">
        ///     The game to which the class belongs.  The <paramref name="game" /> must not be
        ///     <see langword="null" />.
        /// </param>
        protected ElementBase([NotNull] Game game)
        {
            game.ValidateArgument("game");

            _game = game;
            _game.MessageSent += MessageReceived;
            _game.Starting += GameStarting;
            _game.Stopping += GameStopping;
            _game.Started += GameStarted;
            _game.Stopped += GameStopped;
            game.ModeChanging += ModeChanging;
            game.ModeChanged += ModeChanged;
        }

        /// <summary>
        ///     Gets a unique name that can be used to identify this object.
        /// </summary>
        /// <value>The unique name of this item.</value>
        /// <remarks>The name must never be <see langword="null" />, empty or whitespace.</remarks>
        public abstract string Name { get; }

        /// <summary>
        ///     Gets the game to which the entity belongs.
        /// </summary>
        /// <value>The game to which teh entity belongs.</value>
        public Game Game
        {
            get { return _game; }
        }

        /// <summary>
        ///     Copies the property values for the element in one mode to another mode.
        /// </summary>
        /// <param name="source">The source mode of the property values.</param>
        /// <param name="destination">The destination mode of the property values.</param>
        /// <param name="excludedProperties">A list of the names of the properties to exclude from the copy.</param>
        public virtual void CopyModeProperties(Mode source, Mode destination, IEnumerable<string> excludedProperties)
        {}

        /// <summary>
        ///     Logs the specified message (substituting the arguments supplied) to the debug log.
        /// </summary>
        /// <param name="format">The format string (see <see cref="string.Format(string,object)" /> for details).</param>
        /// <param name="arguments">The arguments to substitute.</param>
        /// <remarks>Information about the time and source instance is also logged.</remarks>
        [StringFormatMethod("format")]
        protected void LogToDebug([NotNull] string format, [CanBeNull] params object[] arguments)
        {
            if(!(Debugger.IsAttached && Debugger.IsLogging()))
                return;

            string message;

            if(arguments == null)
                message = format;
            else
                message = string.Format(CultureInfo.InvariantCulture, format, arguments);

            Debug.WriteLine("{0:HH:mm:ss.fff} : {1} : {2} - {3}", DateTime.Now, GetType().Name, Name, message);
        }

        /// <summary>
        ///     Logs the specified message (substituting the arguments supplied) to the trace log.
        /// </summary>
        /// <param name="level">The level of trace to which to log.</param>
        /// <param name="format">The format string (see <see cref="string.Format(string,object)" /> for details).</param>
        /// <param name="arguments">The arguments to substitute.</param>
        /// <remarks>Information about the time and source instance is also logged.</remarks>
        [StringFormatMethod("format")]
        protected void LogToTrace(TraceLevel level, [NotNull] string format, [CanBeNull] params object[] arguments)
        {
            string message;

            if(arguments == null)
                message = format;
            else
                message = string.Format(CultureInfo.InvariantCulture, format, arguments);

            switch(level)
            {
                case TraceLevel.Off:
                    break;
                case TraceLevel.Error:
                    Trace.TraceError("{0:HH:mm:ss.fff} : {1} : {2} - {3}", DateTime.Now, GetType().Name, Name, message);
                    break;
                case TraceLevel.Warning:
                    Trace.TraceWarning("{0:HH:mm:ss.fff} : {1} : {2} - {3}", DateTime.Now, GetType().Name, Name, message);
                    break;
                case TraceLevel.Info:
                    Trace.TraceInformation("{0:HH:mm:ss.fff} : {1} : {2} - {3}", DateTime.Now, GetType().Name, Name, message);
                    break;
                case TraceLevel.Verbose:
                    Trace.WriteLine(string.Format(CultureInfo.InvariantCulture, "{0:HH:mm:ss.fff} : {1} : {2} - {3}", DateTime.Now, GetType().Name, Name, message));
                    break;
                default:
                    throw new ArgumentOutOfRangeException("level", level, "'level' not a valid value.");
            }
        }

        /// <summary>
        ///     Called when a property has changed and the <see cref="DisposableNotifyObject.PropertyChanged" /> event should be
        ///     raised.
        /// </summary>
        /// <param name="propertyName">The name of the property that has changed.</param>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        [NotifyPropertyChangedInvocator]
        protected override void OnPropertyChanged(string propertyName)
        {
            LogToDebug("Property changing: " + propertyName);
            base.OnPropertyChanged(propertyName);
            LogToDebug("Property changed: " + propertyName);
        }

        /// <summary>
        ///     Called when the mode has changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="ModeChangeEventArgs" /> instance containing the event data.</param>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private void ModeChanged([NotNull] object sender, [NotNull] ModeChangeEventArgs e)
        {
            try
            {
                OnModeChanged(e.PreviousMode, e.NewMode);
            }
            catch(Exception ex)
            {
                ErrorManager.OnUnhandledException(ex);
            }
        }

        /// <summary>
        ///     Called when the active mode has changed.
        /// </summary>
        /// <param name="previousMode">The previous mode.</param>
        /// <param name="newMode">The new mode.</param>
        protected virtual void OnModeChanged([CanBeNull] Mode previousMode, [NotNull] Mode newMode)
        {}

        /// <summary>
        ///     Called when the mode is about to change.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="ModeChangeEventArgs" /> instance containing the event data.</param>
        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
        private void ModeChanging([NotNull] object sender, [NotNull] ModeChangeEventArgs e)
        {
            try
            {
                OnModeChanging(e.PreviousMode, e.NewMode);
            }
            catch(Exception ex)
            {
                ErrorManager.OnUnhandledException(ex);
            }
        }

        /// <summary>
        ///     Called when the active mode is about to change.
        /// </summary>
        /// <param name="previousMode">The previous mode.</param>
        /// <param name="newMode">The new mode.</param>
        /// <exception cref="System.NotImplementedException"></exception>
        protected virtual void OnModeChanging([CanBeNull] Mode previousMode, [NotNull] Mode newMode)
        {}

        /// <summary>
        ///     Called when the game has stopped playing.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        /// <exception cref="System.NotImplementedException"></exception>
        private void GameStopped([NotNull] object sender, [NotNull] EventArgs e)
        {
            OnGameStopped();
        }

        /// <summary>
        ///     Called when the game has started playing.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        /// <exception cref="System.NotImplementedException"></exception>
        private void GameStarted([NotNull] object sender, [NotNull] EventArgs e)
        {
            OnGameStarted();
        }

        /// <summary>
        ///     Called when the game is about to stop playing.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        /// <exception cref="System.NotImplementedException"></exception>
        private void GameStopping([NotNull] object sender, [NotNull] EventArgs e)
        {
            OnGameStopping();
        }

        /// <summary>
        ///     Called when the game is about to start playing.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        /// <exception cref="System.NotImplementedException"></exception>
        private void GameStarting([NotNull] object sender, [NotNull] EventArgs e)
        {
            OnGameStarting();
        }

        /// <summary>
        ///     Called when the game has stopped playing.
        /// </summary>
        protected virtual void OnGameStopped()
        {}

        /// <summary>
        ///     Called when the game has started playing.
        /// </summary>
        protected virtual void OnGameStarted()
        {}

        /// <summary>
        ///     Called when the game is about to stop playing.
        /// </summary>
        protected virtual void OnGameStopping()
        {}

        /// <summary>
        ///     Called when the game is about to start playing.
        /// </summary>
        protected virtual void OnGameStarting()
        {}

        /// <summary>
        ///     Called whenever a message is received.
        /// </summary>
        /// <param name="message">The message.</param>
        protected virtual void OnMessageReceived([NotNull] IMessage message)
        {}

        /// <summary>
        ///     Called when a message is received from the game.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="MessageSentEventArgs" /> instance containing the event data.</param>
        private void MessageReceived([CanBeNull] object sender, [NotNull] MessageSentEventArgs e)
        {
            LogToDebug("Message received: " + e.Message);

            OnMessageReceived(e.Message);
        }
    }
}